//
//  File.swift
//  
//
//  Created by 钟志远 on 2021/4/16.
//

import Foundation
import Vapor
import Fluent
import JWT

final class User: Model, Content {
    static var schema: String = "user"
    
    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "user_name")
    var userName: String
    
    @Field(key: "password")
    var password: String
    
    @Timestamp(key: "created_at", on: .create)
    var createdAt: Date?
    
    @Timestamp(key: "updated_at", on: .update)
    var updatedAt: Date?
    

    @OptionalChild(for: \.$user)
    var userInfo: UserInfo?

    @Children(for: \.$user)
    var files: [FileModel]

    required init() {
        
    }
    
    init(userName: String, password: String) {
        self.userName = userName
        self.password = password
    }
}

//MARK: white list
extension User {
    enum CodingKeys : CodingKey {
        case id
        case userName
        case userInfo
        case createdAt
        case updatedAt
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(userName, forKey: .userName)
        try container.encode(userInfo, forKey: .userInfo)
        try container.encode(createdAt, forKey: .createdAt)
        try container.encode(updatedAt, forKey: .updatedAt)
    }
    
}


//MARK: JWT
struct UserWithToken: Content {
    var token: String
    var user: User
}

extension User: Authenticatable {

    func generateJwtPayload(req: Request) throws -> String {
        return try req.jwt.sign(UserJWTPayload.init(id: id))
    }

    func getTokenContent(req: Request, db: Database? = nil) throws -> EventLoopFuture<UserWithToken> {
        return $userInfo.get(reload: true, on: db ?? req.db)
            .flatMapThrowing { _ in
                 try UserWithToken(token: self.generateJwtPayload(req: req), user: self)
            }
    }
    
}

struct UserJWTPayload: JWTPayload {
    
    // Maps the longer Swift property names to the
    // shortened keys used in the JWT payload.
    enum CodingKeys: String, CodingKey {
        case subject = "sub"
        case expiration = "exp"
        case id = "id"
    }

    // The "sub" (subject) claim identifies the principal that is the
    // subject of the JWT.
    var subject: SubjectClaim = "pan"

    // The "exp" (expiration time) claim identifies the expiration time on
    // or after which the JWT MUST NOT be accepted for processing.
    var expiration: ExpirationClaim = .init(value: .init(timeIntervalSinceNow: 60 * 60 * 72))
    
    func verify(using signer: JWTSigner) throws {
        try self.expiration.verifyNotExpired()
        return
    }

    var id: UUID?
}

struct UserAuthenticator: JWTAuthenticator {

    typealias Payload = UserJWTPayload
    
    func authenticate(jwt: UserJWTPayload, for request: Request) -> EventLoopFuture<Void> {
        guard let id = jwt.id else {
            return request.eventLoop.makeFailedFuture(Abort(.unauthorized))
        }
        
        return User.find(id, on: request.db)
            .unwrap(or: Abort(.unauthorized))
            .map{request.auth.login($0)}

    }
    
}
